package tcp

import (
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"math"
	"strconv"

	"gitee.com/acquisition_driver_development/magus_iot/magus/golib/sock"

	"gitee.com/acquisition_driver_development/magus_iot/magus/logs"

	"gitee.com/acquisition_driver_development/magus_iot/common_library/hostlink"
)

type TcpMaster struct {
	DA1 uint16 //目标PLC节点号
	DA2 uint16 //目标设备单元号
	SA1 uint16 //源设备节点号

	Tid uint16 //序列
	hostlink.Master
}

func (this *TcpMaster) Initialize(DA1, DA2, SA1 uint16) (err error) {
	this.DA1 = DA1
	this.DA2 = DA2
	this.SA1 = SA1
	this.Master.Initialize()
	return
}

func (this *TcpMaster) MakeRequest(group *hostlink.Group) (cmd []byte) {

	//fins报文部分
	data := make([]byte, 26)
	data[0], data[1], data[2], data[3] = 0x46, 0x49, 0x4E, 0x53     //'FINS'
	data[4], data[5], data[6], data[7] = 0x00, 0x00, 0x00, 0x1A     //报文长度
	data[8], data[9], data[10], data[11] = 0x00, 0x00, 0x00, 0x02   //命令代码
	data[12], data[13], data[14], data[15] = 0x00, 0x00, 0x00, 0x00 //错误代码
	data[16], data[17], data[18], data[19] = 0x80, 0x00, 0x02, 0x00 //ICF RSV GCT DNA
	data[20], data[21], data[22], data[23] = byte(this.DA1), byte(this.DA2), 0x00, byte(this.SA1)
	data[24], data[25] = 0x00, 0x00 //SA2 SID

	//地址部分
	cmd = make([]byte, 8)
	cmd[0] = 0x01
	cmd[1] = 0x01 //读 0101 ;  写 0102
	wordcode, err := strconv.ParseUint(group.WordCode, 16, 32)
	if err != nil {
		return nil
	}
	cmd[2] = byte(uint16(wordcode))
	cmd[3] = byte(uint16(group.BeginAddress) >> 8)
	cmd[4] = byte(uint16(group.BeginAddress))
	if group.IfBite {
		cmd[5] = group.BeginBit
	}
	cmd[6] = byte(uint16(group.GetSize()) >> 8)
	cmd[7] = byte(uint16(group.GetSize()))

	cmd = append(data, cmd...)
	return
}

func (this *TcpMaster) Write(conn io.Writer, data []byte) (n int, err error) {
	if len(data) < 20 { //最短报文
		err = errors.New("data error")
		return
	}
	n, err = conn.Write(data)
	logs.Debug("S: % 2X", data)
	return
}

func (this *TcpMaster) Read(conn io.Reader) (buf []byte, err error) {
	data := make([]byte, 8)
	_, err = sock.ReadTimeout(conn, data, 2000)
	if err != nil {
		return
	}

	if string(data[:4]) != "FINS" {
		err = errors.New("error protocol: " + string(data[:4]))
		return
	}
	l := binary.BigEndian.Uint32(data[4:8])

	if l == 0 {
		err = errors.New("not data left")
		return
	}
	buf = make([]byte, l)
	_, err = sock.ReadTimeout(conn, buf, 2000)
	if err != nil {
		return
	}
	logs.Debug("R: %2X", append(data, buf...))

	//buf, err = this.CheckData(buf)
	return
}

func (this *TcpMaster) CheckData(data []byte) (buf []byte, err error) {
	l := len(data)
	if l < 23 {
		err = errors.New("error data")
		return
	}
	if data[3] != 0x02 { //命令代码
		err = errors.New("error command code")
		return
	}
	if data[7] != 0x00 { //错误代码
		err = errors.New("error code ")
		return
	}
	if data[20] != 0x00 || data[21] != 0x00 {
		err = errors.New("end code error")
		return
	}

	return data[22:], nil
}

//---------------------------------握手操作---------------------------------------

//握手请求
func (this *TcpMaster) StartAbove() (data []byte) {
	data = make([]byte, 20)
	copy(data[:4], "FINS")
	data[4], data[5], data[6], data[7] = 0, 0, 0, 0x0C
	data[8], data[9], data[10], data[11] = 0, 0, 0, 0
	data[12], data[13], data[14], data[15] = 0, 0, 0, 0
	data[16], data[17], data[18], data[19] = 0, 0, 0, byte(this.SA1)
	return
}

/*
*验证握手响应
*	错误码(16进制):
*	00000000           正常
*	00000001           头不是‘FINS’ (ASCII code)。
*	00000002           数据太长。
*	00000003           不支持的命令。
*	00000020           所有的连接被占用。
*	00000021           指定的节点已经连接。
*	00000022           未被指定的IP地址试图访问一个被保护的节点。
*	00000023           客户端FINS节点地址超范围。
*	00000024           相同的FINS节点地址已经被使用。
*	00000025           所有可用的节点地址都已使用。
 */
func (this *TcpMaster) ParseAbove(data []byte) (err error) {
	if binary.BigEndian.Uint32(data[:4]) != 1 { //功能码
		err = errors.New(fmt.Sprintf("error func code:% 2X", data[:4]))
		return
	}
	if binary.BigEndian.Uint32(data[4:8]) != 0 { //错误码
		err = errors.New(fmt.Sprintf("error  code:% 2X", data[4:8]))
	}
	return
}

//----------------------------------控制-----------------------------------------
//控制报文
func DoFinsTcpControlCommand(point *hostlink.Point) (cmd []byte, err error) {
	cmd = make([]byte, 6)
	cmd[0], cmd[1] = 0x01, 0x02
	worcode, err := strconv.ParseUint(point.WordCode, 16, 8)
	if err != nil {
		return nil, err
	}
	cmd[2] = byte(uint(worcode))
	cmd[3] = byte(uint16(point.Address) >> 8)
	cmd[4] = byte(uint16(point.Address))
	if point.IfBit {
		cmd[5] = point.Bit
	} else {
		cmd[5] = 0x00
	}
	data := makewritedata(point)
	cmd = append(cmd, data...)
	return
}

func makewritedata(point *hostlink.Point) (data []byte) {
	data = make([]byte, 2)

	//全按照大端编码
	switch point.Type {
	case hostlink.HOSTLINK_COILS:
		data[0] = 0x00
		data[1] = 0x01
		data = append(data, byte(uint(point.Value))&1)

	case hostlink.HOSTLINK_UINT16, hostlink.HOSTLINK_UINT16_L, hostlink.HOSTLINK_INT16,
		hostlink.HOSTLINK_INT16_L:

		data[0] = 0x00
		data[1] = 0x01
		data = append(data, byte(uint(point.Value)>>8), byte(uint(point.Value)))
	case hostlink.HOSTLINK_INT32, hostlink.HOSTLINK_INT32_H, hostlink.HOSTLINK_INT32_L, hostlink.HOSTLINK_UINT32,
		hostlink.HOSTLINK_UINT32_H, hostlink.HOSTLINK_UINT32_L:
		data[0] = 0x00
		data[1] = 0x02
		data = append(data, byte(uint32(point.Value)>>24),
			byte(uint32(point.Value)>>16),
			byte(uint32(point.Value)>>8),
			byte(uint32(point.Value)))
	case hostlink.HOSTLINK_FLOAT, hostlink.HOSTLINK_FLOAT_H, hostlink.HOSTLINK_FLOAT_L:
		data[0] = 0x00
		data[1] = 0x02
		value := math.Float32bits(float32(point.Value))
		data = append(data, byte(value>>24),
			byte(value>>16),
			byte(value>>8),
			byte(value))
	case hostlink.HOSTLINK_UINT64_H, hostlink.HOSTLINK_INT64_H:
		data[0] = 0x00
		data[1] = 0x04
		value := uint64(point.Value)
		data = append(data, byte(value>>56),
			byte(value>>48),
			byte(value>>40),
			byte(value>>32),
			byte(value>>24),
			byte(value>>16),
			byte(value>>8),
			byte(value))
	}
	return
}
